package huu.phong.youtube;

import huu.phong.youtube.ConstantValue.SearchType;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Container;
import java.awt.Cursor;
import java.awt.Desktop;
import java.awt.Dimension;
import java.awt.Font;
import java.awt.Insets;
import java.awt.KeyEventDispatcher;
import java.awt.KeyboardFocusManager;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.datatransfer.Clipboard;
import java.awt.datatransfer.StringSelection;
import java.awt.event.AWTEventListener;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.KeyAdapter;
import java.awt.event.KeyEvent;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowStateListener;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URLEncoder;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.Box;
import javax.swing.BoxLayout;
import javax.swing.ButtonGroup;
import javax.swing.DefaultListModel;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JCheckBoxMenuItem;
import javax.swing.JFileChooser;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTextField;
import javax.swing.KeyStroke;
import javax.swing.SwingUtilities;
import javax.swing.ToolTipManager;
import javax.swing.UIManager;

import uk.co.caprica.vlcj.player.MediaPlayer;
import uk.co.caprica.vlcj.player.MediaPlayerEventAdapter;
import uk.co.caprica.vlcj.player.MediaPlayerFactory;
import uk.co.caprica.vlcj.player.embedded.EmbeddedMediaPlayer;
import uk.co.caprica.vlcj.player.embedded.FullScreenStrategy;
import uk.co.caprica.vlcj.player.embedded.videosurface.CanvasVideoSurface;
import uk.co.caprica.vlcj.runtime.RuntimeUtil;

import com.sun.jna.NativeLibrary;

public class YouTube extends JFrame {
	private static final long serialVersionUID = -5050145032412743007L;
	
	public static final int ACTION_SEARCH_VIDEO = 0;
	public static final int ACTION_GET_VIDEO_OF_PLAYLIST = 1;
	public static final int ACTION_GET_VIDEO_OF_USERNAME = 2;
	public static final int ACTION_SEARCH_VIDEO_WITH_USERNAME = 3;
	
	public static final Color DEFAULT_BACKGROUND = new Color(0f, 0f, 0f, 0.7f);
	public static final Font DEFAULT_FONT = new Font("Dialog", Font.BOLD, 12);
	public static final ImageIcon DEFAULT_SUGGESTING = new ImageIcon(YouTube.class.getResource("/images/suggesting.gif"));
	
	private JList songs, relativeSongs, recentSongs;
	private DefaultListModel modelSongs, modelRelativeSongs, modelRecentSongs;
	private JButton search;
	private JTextField value;
	private Toolkit toolkit = Toolkit.getDefaultToolkit();
	private Clipboard clipboard = toolkit.getSystemClipboard();
	private Configure configure;
	private Class<?> object = this.getClass();
	private JLabel info, songInfo, labelSearch, iconStatus,startDuration, endDuration, storyBoardQuality;
	private ColorSlider slider;
	private History<List<Video>> history;
	private Dimension dimension;
	private YouTubeAPI youtube = YouTubeAPI.getInstance();
	private EmbeddedMediaPlayer player;
	private Thread updateSong = null;
	private Thread updateRelativeSong = null;
	private int currentIndexSong = 0;
	private Video currentSong;
	private String idPlaylist = "";
	private int lastPageRelativeSong = 1;
	private String lastValueRelativeSong = null;
	private int currentAction = ACTION_SEARCH_VIDEO;
	private JButton play;
	private JFileChooser chooser = new JFileChooser();
	private VideoCanvas playerComponent;
	private CanvasVideoSurface videoSurface;
	private MediaPlayerFactory playerFactory;
	private JPanel panelSearch, panelStatus, panelPlayer;
	private JScrollPane scrollPane;
	private JScrollPane scrollPaneRelative;
	private JScrollPane scrollPaneRecent;
	private Map<KeyStroke, Action> actionMap = new HashMap<KeyStroke, Action>();
	private StoryBoard storyBoard;
	private String userId = null;
	private Thread suggesting;
	private boolean fullScreen = false;
	private Timer timer = new Timer();
	
	
	private Thread suggetThread;
	private Queue<Thread> process = new ArrayDeque<Thread>();
	
	private JPopupMenu completer;
	private DefaultListModel modelAutoCompleted;
	private JList autoCompleted = new JList(modelAutoCompleted = new DefaultListModel());
	private boolean searchSuggest = false;
	
	private JPopupMenu qualitySelector;
	
	private JFrame frameFullScreen;
	private Overlay overlay = new Overlay();
	
	private JPopupMenu popupConfigure;
	private JPopupMenu popupRecentSongs;
	
	private JPopupMenu popupSearchCondition;
	
	public YouTube() {
		// Support full screen
		frameFullScreen = new JFrame(Configure.getInstance().title);
		frameFullScreen.addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent arg0) {
				player.setFullScreen(false);
			}
		});
		frameFullScreen.setIconImage(toolkit.getImage(object.getResource("/images/app.png")));
		frameFullScreen.setUndecorated(true);
		frameFullScreen.setSize(toolkit.getScreenSize());
		//Default setting
		JPopupMenu.setDefaultLightWeightPopupEnabled(false);
		
		setSize(920, 700);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setIconImage(toolkit.getImage(object.getResource("/images/app.png")));
		configure = Configure.getInstance();
		history = new History<List<Video>>();
		addWindowListener(new WindowAdapter() {
			@Override
			public void windowClosing(WindowEvent arg0) {
				configure.save();
			}
		});
		addComponentListener(new ComponentAdapter() {
			public void componentMoved(ComponentEvent e) {
				configure.location = getLocation();
			}
		});
		addWindowStateListener(new WindowStateListener() {
			public void windowStateChanged(WindowEvent arg0) {
				configure.windowsState = arg0.getNewState();
				if (arg0.getNewState() == 7){
					player.setOverlay(null);
					player.enableOverlay(false);
				}else if (arg0.getNewState() == 6){
					timer.schedule(new TimerTask(){
						@Override
						public void run() {
							setOverlay();
						}
						
					}, 400);
				}
			}
		});
		suggetThread = new Thread(){
			public void run(){
				while (true){
					while (!process.isEmpty()){
						while (process.size() > 1){
							process.poll();
						}
						Thread thread = process.poll();
						thread.start();
						try {
							thread.join();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					synchronized (suggetThread) {
						try {
							suggetThread.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						};
					}
				}
			}
		};
		suggetThread.start();
		suggesting = new Thread(){
			public void run(){
				while (true){
					while (searchSuggest){
						if (value != null) value.getGraphics().drawImage(DEFAULT_SUGGESTING.getImage(), value.getWidth() - 20, (value.getHeight() - 16) / 2, 16, 16, DEFAULT_SUGGESTING.getImageObserver());
						try {
							Thread.sleep(100);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
					synchronized (suggesting) {
						try {
							if (value != null) {
//								value.invalidate();
//								value.validate();
								value.repaint();
							}
							suggesting.wait();
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
					}
				}
			}
		};
		suggesting.start();
		NativeLibrary.addSearchPath(RuntimeUtil.getLibVlcLibraryName(),	new File(".").getAbsolutePath());
	    String[] args = new String[]{"--network-caching=3000", "--plugins-cache", "--no-video-title-show", "--no-snapshot-preview"};
		playerFactory = new MediaPlayerFactory(args);
		playerFactory.setUserAgent("Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1", "Mozilla/5.0 (Windows NT 6.1; WOW64; rv:14.0) Gecko/20100101 Firefox/14.0.1");
		playerComponent = new VideoCanvas();
		final JPopupMenu popupMenuPlayer = createPopupPlayer();
		playerComponent.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e){
				if (SwingUtilities.isLeftMouseButton(e) && e.getClickCount() == 2){
					player.toggleFullScreen();
					playerComponent.repaint();
				}else if(SwingUtilities.isRightMouseButton(e)){
					popupMenuPlayer.show(playerComponent, e.getX(), e.getY());
				}
			}
		});
		playerComponent.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent arg0) {
				if (arg0.getUnitsToScroll() > 0){
					addVolume(-5);
				}else{
					addVolume(5);
				}
			}
		});
		FullScreenStrategy fullScreenStrategy = new FullScreenStrategy() {
			private boolean fullScreen = false;
			private Dimension dimension = YouTube.this.getSize();
			private Point location = YouTube.this.getLocation();
			
			public boolean isFullScreenMode() {
				return fullScreen;
			}
			
			public void exitFullScreenMode() {
				if (YouTube.this.fullScreen){
					toggleFullScreen();
					return;
				}
				scrollPane.setVisible(true);
				scrollPaneRelative.setVisible(true);
				panelSearch.setVisible(true);
				panelStatus.setVisible(true);
				YouTube.this.setSize(dimension);
				YouTube.this.invalidate();
				YouTube.this.validate();
				YouTube.this.requestFocus();
				YouTube.this.setLocation(location);
				playerComponent.repaint();
				fullScreen = false;
				setOverlay();
			}
			
			public void enterFullScreenMode() {
				if (YouTube.this.fullScreen){
					toggleFullScreen();
					return;
				}
				scrollPane.setVisible(false);
				scrollPaneRelative.setVisible(false);
				panelSearch.setVisible(false);
				panelStatus.setVisible(false);
				dimension = YouTube.this.getSize();
				location = YouTube.this.getLocation();
				YouTube.this.setExtendedState(JFrame.MAXIMIZED_BOTH);
				YouTube.this.invalidate();
				YouTube.this.validate();
				YouTube.this.requestFocus();
				playerComponent.repaint();
				fullScreen = true;
				setOverlay();
			}
		};
//		fullScreenStrategy = new DefaultFullScreenStrategy(Main.this);
		player = playerFactory.newEmbeddedMediaPlayer(fullScreenStrategy);
		playerComponent.setBackground(Color.BLACK);
		videoSurface = playerFactory.newVideoSurface(playerComponent);
		player.setVideoSurface(videoSurface);
		player.setEnableKeyInputHandling(false);
		player.setEnableMouseInputHandling(false);
		player.addMediaPlayerEventListener(new MediaPlayerEventAdapter() {
			public void timeChanged(MediaPlayer arg0, long arg1) {
				if (!slider.dragged) slider.setValue(player.getTime());
				startDuration.setText(YouTubeAPI.toDuration(slider.value / 1000));
			}
			
			public void stopped(MediaPlayer arg0) {
				play.setIcon(getImage("play.png"));
				overlay.setTitle(null, null);
			}
			
			public void seekableChanged(MediaPlayer arg0, int arg1) {
				startDuration.setText(YouTubeAPI.toDuration(slider.value / 1000));
			}
			
			public void playing(MediaPlayer arg0) {
				if (!slider.seeking) slider.setMax(player.getLength());
				slider.seeking = false;
				endDuration.setText(YouTubeAPI.toDuration(slider.max / 1000));
				play.setIcon(getImage("pause.png"));
				playerComponent.setTitle("","");
				overlay.setTitle(currentSong.getTitle(), currentSong.currentFormat);
			}
			
			public void paused(MediaPlayer arg0) {
				play.setIcon(getImage("play.png"));
			}
			
			public void finished(MediaPlayer arg0) {
				play.setIcon(getImage("play.png"));
				if (configure.isPlayRelative && !modelRelativeSongs.isEmpty()){
					play((Video)modelRelativeSongs.get(0));
				} else {
					if (++currentIndexSong < modelSongs.getSize()){
						play((Video)modelSongs.get(currentIndexSong));
						songs.setSelectedIndex(currentIndexSong);
						songs.ensureIndexIsVisible(currentIndexSong);
					}
					if (currentIndexSong == modelSongs.getSize() - 1 && configure.update && updateSong == null){
						switch (currentAction) {
							case ACTION_SEARCH_VIDEO: if (!configure.lastValueSong.equals("")) updateSong(); break;
							case ACTION_GET_VIDEO_OF_PLAYLIST: if (idPlaylist != null) updateSongOfPlaylist(); break;
							case ACTION_GET_VIDEO_OF_USERNAME: if (userId != null) updateSongOfUser(); break;
							default: if (!configure.lastValueSong.equals("")) updateSong(); break;
						}
					}
				}
				overlay.setTitle(null, null);
			}
			
			public void error(MediaPlayer arg0) {
				setStatus("CAN'T PLAY THIS VIDEO");
			}
			
			public void buffering(MediaPlayer arg0, float arg1) {
				if (arg1 == 100.0f){
					slider.setRange(0);
					overlay.setWait(false);
				}else{
					slider.setRange((long) (arg1 * 1.0 * player.getLength() / 100.0));
					if (slider.getValue() > 0) overlay.setWait(true);
				}
				overlay.setInformation("Buffering: " + arg1 + " %");
			}
		});
		
		Container container = getContentPane();
		container.setLayout(new BorderLayout());
		container.add(panelSearch = initPanelSearch(), BorderLayout.NORTH);
		JPanel panelList = new JPanel();
		panelList.setLayout(new BorderLayout());
		panelList.add(scrollPane = new JScrollPane(songs = new JList(modelSongs = new DefaultListModel()),
				JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
				JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED), BorderLayout.WEST);
		dimension = scrollPane.getMaximumSize();
		dimension.width = configure.resultWidth;
		scrollPane.setPreferredSize(dimension);
		scrollPane.setMaximumSize(dimension);
		scrollPane.setMinimumSize(dimension);
		scrollPane.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent arg0) {
				if (configure.update && updateSong == null && configure.songs.size() - 1 == songs.getLastVisibleIndex() && arg0.getUnitsToScroll() > 0){
					switch (currentAction) {
						case ACTION_SEARCH_VIDEO: if (!configure.lastValueSong.equals("")) updateSong(); break;
						case ACTION_GET_VIDEO_OF_PLAYLIST: if (idPlaylist != null) updateSongOfPlaylist(); break;
						case ACTION_GET_VIDEO_OF_USERNAME: if (userId != null) updateSongOfUser(); break;
						default: if (!configure.lastValueSong.equals("")) updateSong(); break;
					}
				}
			}
		});
		scrollPane.addMouseMotionListener(new MouseAdapter() {
			public void mouseMoved(MouseEvent e){
				if (e.getX() + 1 >= scrollPane.getPreferredSize().getWidth() ){
					scrollPane.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
				}else{
					scrollPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
				}
			}
			
			public void mouseDragged(MouseEvent e){
				scrollPane.setCursor(Cursor.getPredefinedCursor(Cursor.E_RESIZE_CURSOR));
				dimension = scrollPane.getMaximumSize();
				if (e.getX() + 1 >= 350){
					dimension.width = e.getX() + 1;
				}else{
					dimension.width = 350;
				}
				scrollPane.setPreferredSize(dimension);
				scrollPane.setMaximumSize(dimension);
				scrollPane.setMinimumSize(dimension);
				scrollPane.invalidate();
				scrollPane.validate();
				YouTube.this.invalidate();
				YouTube.this.validate();
				configure.resultWidth = dimension.width;
			}
		});
		scrollPane.addMouseListener(new MouseAdapter() {
			public void mouseExited(MouseEvent e){
				scrollPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
			
			public void mouseReleased(MouseEvent e){
				scrollPane.setCursor(Cursor.getPredefinedCursor(Cursor.DEFAULT_CURSOR));
			}
		});
		songs.setCellRenderer(new SongRenderer());
		songs.setComponentPopupMenu(createPopupMenuSong());
		songs.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					play();
				}else if(arg0.getKeyCode() == KeyEvent.VK_DELETE){
					deleteLinks();
				}else if (arg0.getKeyCode() == KeyEvent.VK_DOWN){
					if (configure.update && updateSong == null && configure.songs.size() - 1 == songs.getSelectedIndex()){
						switch (currentAction) {
							case ACTION_SEARCH_VIDEO: if (!configure.lastValueSong.equals("")) updateSong(); break;
							case ACTION_GET_VIDEO_OF_PLAYLIST: if (idPlaylist != null) updateSongOfPlaylist(); break;
							case ACTION_GET_VIDEO_OF_USERNAME: if (userId != null) updateSongOfUser(); break;
							default: if (!configure.lastValueSong.equals("")) updateSong(); break;
						}
					}
				}
			}
		});
		songs.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e) {
				if (SwingUtilities.isRightMouseButton(e)) {
					if (songs.getSelectedIndices().length <= 1){
						if (!configure.songs.isEmpty()) songs.setSelectedIndex(songs.locationToIndex(e.getPoint()));
					}
				}else{
					if (e.getClickCount() == 2) {
						play();
					}
				}
			}
		});
//		songs.addMouseMotionListener(new MouseAdapter() {
//			public void mouseMoved(MouseEvent e){
//				int index = songs.locationToIndex(e.getPoint());
//				if (index == -1 || configure.songs.isEmpty()) return;
//				songs.setToolTipText(configure.songs.get(index).toString());
//			}
//		});
		panelPlayer = new JPanel();
		panelPlayer.setLayout(new BorderLayout());
		scrollPaneRelative = new JScrollPane(relativeSongs = new JList(modelRelativeSongs = new DefaultListModel()), JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		scrollPaneRelative.addMouseWheelListener(new MouseWheelListener() {
			public void mouseWheelMoved(MouseWheelEvent arg0) {
				if (modelRelativeSongs.getSize() < 100 && configure.update && lastValueRelativeSong != null && updateRelativeSong == null && modelRelativeSongs.getSize() - 1 == relativeSongs.getLastVisibleIndex() && arg0.getUnitsToScroll() > 0){
					updateRelativeSong();
				}
			}
		});
		relativeSongs.setComponentPopupMenu(createPopupMenu(relativeSongs));
		relativeSongs.setVisibleRowCount(1);
		relativeSongs.setLayoutOrientation(JList.HORIZONTAL_WRAP);
		relativeSongs.setFixedCellWidth(300);
		relativeSongs.setCellRenderer(new SongRenderer());
		relativeSongs.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e){
				if (SwingUtilities.isRightMouseButton(e)) {
					if (relativeSongs.getSelectedIndices().length <= 1){
						if (!modelRelativeSongs.isEmpty()) relativeSongs.setSelectedIndex(relativeSongs.locationToIndex(e.getPoint()));
					}
				}else{
					if (e.getClickCount() == 2) {
						play((Video)relativeSongs.getSelectedValue());
					}
				}
			}
		});
//		relativeSongs.addMouseMotionListener(new MouseAdapter() {
//			public void mouseMoved(MouseEvent e){
//				int index = relativeSongs.locationToIndex(e.getPoint());
//				if (index == -1) return;
//				relativeSongs.setToolTipText((modelRelativeSongs.get(index)).toString());
//			}
//		});
		relativeSongs.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					play((Video)relativeSongs.getSelectedValue());
				}else if (arg0.getKeyCode() == KeyEvent.VK_RIGHT){
					if (configure.update && updateRelativeSong == null && modelRelativeSongs.getSize() - 1 == relativeSongs.getSelectedIndex()){
						updateRelativeSong();
					}
				}
			}
		});
		dimension = scrollPaneRelative.getMaximumSize();
		dimension.height = 110;
		scrollPaneRelative.setPreferredSize(dimension);
		scrollPaneRelative.setMaximumSize(dimension);
//		scrollPaneRelative.setMinimumSize(dimension);
		panelPlayer.add(scrollPaneRelative, BorderLayout.SOUTH);
		panelPlayer.add(playerComponent, BorderLayout.CENTER);
//		TitleLabel a;
//		panelPlayer.add(a = new TitleLabel(), BorderLayout.CENTER);
//		panelPlayer.setOpaque(false);
//		a.setOpaque(false);
//		a.setBackground(new Color(1f, 1f, 1f, 1.0f));
		panelList.add(panelPlayer, BorderLayout.CENTER);
//		panelList.add(scrollPaneRelative, BorderLayout.SOUTH);
		container.add(panelList, BorderLayout.CENTER);
		
		panelStatus = new JPanel();
		dimension = panelStatus.getPreferredSize();
		dimension.height = 30;
		panelStatus.setPreferredSize(dimension);
		panelStatus.setLayout(new BorderLayout());
		
		JPanel leftStatus = new JPanel();
		leftStatus.setOpaque(false);
		leftStatus.setLayout(new BoxLayout(leftStatus, BoxLayout.X_AXIS));
		leftStatus.add(iconStatus = new JLabel(getImage("searching.gif")));
		iconStatus.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
		iconStatus.setVisible(false);
		leftStatus.add(songInfo = new JLabel(configure.status));
		songInfo.setFont(songInfo.getFont().deriveFont(14f));
		songInfo.setBorder(BorderFactory.createEmptyBorder(0, 5, 0, 0));
		panelStatus.add(leftStatus, BorderLayout.WEST);
		
		final JPanel panelSlider = new JPanel();
		panelSlider.addMouseMotionListener(new MouseAdapter() {
			public void mouseMoved(MouseEvent arg0) {
				panelSlider.setFocusCycleRoot(false);
				panelSlider.setFocusable(false);
			}
		});
		dimension = panelSlider.getPreferredSize();
		dimension.height = 25;
		dimension.width = 482;
		panelSlider.setPreferredSize(dimension);
		panelSlider.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, new Color(204, 204, 204)));
		panelSlider.setLayout(new BoxLayout(panelSlider, BoxLayout.LINE_AXIS));
		panelSlider.setOpaque(false);
		play = new JButton();
		play.setMaximumSize(new Dimension(30, 30));
		play.setPreferredSize(new Dimension(30, 30));
		play.setMinimumSize(new Dimension(30, 30));
		play.setIcon(getImage("play.png"));
		play.setContentAreaFilled(false);
		play.setFocusable(false);
		play.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				togglePlay();
			}
		});
		play.addMouseListener(new MouseAdapter() {
			public void mouseEntered(MouseEvent e){
				play.setContentAreaFilled(true);
			}
			
			public void mouseExited(MouseEvent e){
				play.setContentAreaFilled(false);
			}
		});
		panelSlider.add(play);
		panelSlider.add(startDuration = new JLabel("00:00", JLabel.RIGHT));
//		startDuration.setOpaque(true);
		dimension = new Dimension(50, 25);
		startDuration.setPreferredSize(dimension);
		startDuration.setMaximumSize(dimension);
		startDuration.setMinimumSize(dimension);
		slider = new ColorSlider(player);
		slider.addMouseMotionListener(new MouseAdapter() {
			public void mouseMoved(MouseEvent e){
				slider.setToolTipText(YouTubeAPI.toDuration(slider.valueAtPoint(e) / 1000));
        		if (storyBoard != null){
        			try {
						slider.toolTip.setImage(storyBoard.getImage(slider.valueAtPoint(e)));
					} catch (MalformedURLException e1) {
						e1.printStackTrace();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
        		}
			}
			
			public void mouseDragged(MouseEvent e) {
				if (!SwingUtilities.isLeftMouseButton(e)) return;
				slider.value = slider.valueAtPoint(e);
				slider.repaint();
				slider.setToolTipText(YouTubeAPI.toDuration(slider.value / 1000));
				if (storyBoard != null){
        			try {
						slider.toolTip.setImage(storyBoard.getImage(slider.valueAtPoint(e)));
					} catch (MalformedURLException e1) {
						e1.printStackTrace();
					} catch (IOException e1) {
						e1.printStackTrace();
					}
        		}
		        MouseEvent event = new MouseEvent(
                        slider,
                        MouseEvent.MOUSE_MOVED,
                        System.currentTimeMillis(),
                        0,
                        e.getX(),
                        e.getY(),
                        0,
                        false);
                ToolTipManager.sharedInstance().mouseMoved(event);
			}
		});
		panelSlider.add(Box.createRigidArea(new Dimension(5, 25)));
		panelSlider.add(slider);
		panelSlider.add(Box.createRigidArea(new Dimension(5, 25)));
		panelSlider.add(endDuration = new JLabel("00:00", JLabel.LEFT));
		endDuration.setPreferredSize(dimension);
		endDuration.setSize(dimension);
		endDuration.setMaximumSize(dimension);
		endDuration.setMinimumSize(dimension);
		panelSlider.add(storyBoardQuality = new JLabel());
		storyBoardQuality.setPreferredSize(new Dimension(40, 25));
		storyBoardQuality.setMinimumSize(new Dimension(40, 25));
		storyBoardQuality.setMaximumSize(new Dimension(40, 25));
		storyBoardQuality.setVerticalTextPosition(JLabel.CENTER);
		storyBoardQuality.setHorizontalAlignment(JLabel.CENTER);
		storyBoardQuality.setVerticalAlignment(JLabel.CENTER);
		storyBoardQuality.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		storyBoardQuality.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, new Color(204, 204, 204)));
		setStoryQuality();
		storyBoardQuality.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e){
				configure.qualitySoryBoard++;
				if (configure.qualitySoryBoard > 3){
					configure.qualitySoryBoard = 0;
				}
				if (currentSong != null) storyBoard = currentSong.getStoryBoard();
				slider.toolTip.setImage(null);
				setStoryQuality();
			}
		});
		panelSlider.add(info = new JLabel());
		qualitySelector = new JPopupMenu();
		for (final Format format : Format.values()){
			final JMenuItem item = new JMenuItem(format.name());
			item.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					qualitySelector.setVisible(false);
					configure.format = format;
//					info.setIcon(format.getImage());
					info.setText(format.name());
					if (currentSong == null) return;
					try {
						Format oldFormat = currentSong.currentFormat;
						String link = currentSong.getOriginLink(configure.format);
						if (currentSong.currentFormat.compareTo(oldFormat) == 0) return;
						slider.seeking = true;
						slider.plusDuration = slider.value;
						player.playMedia(link + "&begin=" + slider.plusDuration);
						songInfo.setText(currentSong.title + " [" + currentSong.currentFormat + "]");
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			});
			if (format.ordinal() == 7 || format.ordinal() == 10){
				qualitySelector.addSeparator();
			}
			qualitySelector.add(item);
		}
//		info.setIcon(configure.format.getImage());
		info.setText(configure.format.name());
		info.setPreferredSize(new Dimension(80, 25));
		info.setMinimumSize(new Dimension(80, 25));
		info.setMaximumSize(new Dimension(80, 25));
		info.setVerticalTextPosition(JLabel.CENTER);
		info.setHorizontalAlignment(JLabel.CENTER);
		info.setVerticalAlignment(JLabel.CENTER);
		info.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		info.setBorder(BorderFactory.createMatteBorder(0, 1, 0, 0, new Color(204, 204, 204)));
//		info.setOpaque(true);
//		info.setBackground(Color.BLACK);
		info.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e){
				qualitySelector.show(info, 0, - qualitySelector.getPreferredSize().height - 3);
			}
		});
		panelStatus.add(panelSlider, BorderLayout.EAST);
		container.add(panelStatus, BorderLayout.SOUTH);
//		container.add(Box.createRigidArea(new Dimension(1, 0)), BorderLayout.EAST);
//		container.add(Box.createRigidArea(new Dimension(5, 0)), BorderLayout.WEST);
		if (configure.location == null){
			setLocationRelativeTo(null);
		}else{
			setLocation(configure.location);
		}
		setTitle(configure.title);
		setExtendedState(configure.windowsState);
		loadConfigure();
		toolkit.getSystemEventQueue().push(new MyEventQueue());
		setKeys();
		setVisible(true);
		setOverlay();
	}
	
	private void loadConfigure(){
		setSongs(configure.songs, true);
		value.setText(configure.lastValueSong);
	}

	public void setSongs(final List<Video> lists, boolean clear) {
		configure.songs = lists;
		modelSongs.clear();
		history.add(lists);
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				for (Video song : lists) {
					modelSongs.addElement(song);
				}
				songs.setSelectedIndex(0);
				songs.requestFocus();
			}
		});
	}
	
	private void search(){
		String valueSearch = configure.trimSearchValue ? value.getText().trim() : value.getText();
		if (valueSearch.equals("")) return;
		configure.recentValues.addFirst(valueSearch);
		labelSearch.setIcon(getImage("searching.gif"));
		search.setEnabled(false);
		configure.lastValueSong = valueSearch;
		configure.lastPageSong = 1;
		currentAction = ACTION_SEARCH_VIDEO;
		new Thread(){
			public void run(){
				try {
					setSongs(youtube.search(configure.lastValueSong, 1, ""), true);
					search.setEnabled(true);
					labelSearch.setIcon(getImage("search24.png"));
				} catch (Exception e) {
					search.setEnabled(true);
					labelSearch.setIcon(getImage("search24.png"));
					setStatus("NETWORK ERROR");
					e.printStackTrace();
				}
			}
		}.start();
	}

	public void setStatus(String str) {
		songInfo.setText("[" + str + "]");
	}

	private ImageIcon getImage(String name){
		return new ImageIcon(object.getResource("/images/" + name));
	}

	private JPanel initPanelSearch(){
		final JPanel panelSearch = new JPanel();
		panelSearch.setLayout(new BoxLayout(panelSearch, BoxLayout.X_AXIS));
		panelSearch.setBorder(BorderFactory.createEmptyBorder(5, 5, 5, 5));
		panelSearch.add(labelSearch = new JLabel(getImage("search24.png"), JLabel.CENTER));
		labelSearch.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		popupSearchCondition = new JPopupMenu();
		ButtonGroup searchTypeGroup = new ButtonGroup();
		for (final SearchType type : SearchType.values()){
			JCheckBoxMenuItem item = new JCheckBoxMenuItem(type.getDescription());
			if (type.equals(Configure.getInstance().searchType)) item.setSelected(true);
			item.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					configure.searchType = type;
				}
			});
			searchTypeGroup.add(item);
			popupSearchCondition.add(item);
		}
		popupSearchCondition.addSeparator();
		final JCheckBoxMenuItem itemTrimValue = new JCheckBoxMenuItem("Trim value");
		itemTrimValue.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				configure.trimSearchValue = itemTrimValue.isSelected();
			}
		});
		popupSearchCondition.add(itemTrimValue);
		labelSearch.setComponentPopupMenu(popupSearchCondition);
		labelSearch.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e){
				popupSearchCondition.show(panelSearch, 0, panelSearch.getPreferredSize().height);
			}
		});
		panelSearch.add(Box.createHorizontalStrut(5));
		panelSearch.add(value = new JTextField());
		dimension = value.getPreferredSize();
		dimension.width = 317;
		dimension.height += 5;
		value.setPreferredSize(dimension);
		value.setMaximumSize(dimension);
		value.setMaximumSize(dimension);
		value.setSize(dimension);
		panelSearch.add(Box.createHorizontalStrut(5));
		Insets insets = value.getInsets();
		insets.right = 20;
		value.setMargin(insets);
		value.setFont(value.getFont().deriveFont(15f));
		value.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					search();
					completer.setVisible(false);
				}else if (arg0.getKeyCode() == KeyEvent.VK_DOWN){
					if (!modelAutoCompleted.isEmpty()){
						if (autoCompleted.getSelectedIndex() == -1 || autoCompleted.getSelectedIndex() + 1 >= modelAutoCompleted.getSize()){
							autoCompleted.setSelectedIndex(0);
						}else{
							autoCompleted.setSelectedIndex(autoCompleted.getSelectedIndex() + 1);
						}
						value.setText(autoCompleted.getSelectedValue().toString());
						value.setCaretPosition(value.getText().length());
						value.requestFocus();
					}
				} else if (arg0.getKeyCode() == KeyEvent.VK_UP){
					if (!modelAutoCompleted.isEmpty()){
						if (autoCompleted.getSelectedIndex() == -1 || autoCompleted.getSelectedIndex() == 0){
							autoCompleted.setSelectedIndex(modelAutoCompleted.size() - 1);
						}else{
							autoCompleted.setSelectedIndex(autoCompleted.getSelectedIndex() - 1);
						}
						value.setText(autoCompleted.getSelectedValue().toString());
						value.setCaretPosition(value.getText().length());
						value.requestFocus();
					}
				}
			}
			
			public void keyReleased(KeyEvent e){
				if (e.isActionKey() || e.getKeyCode() == KeyEvent.VK_ENTER){
					if (!e.isActionKey()) completer.setVisible(false);
					return;
				}
				if (value.getText().trim().equals("")){
					if (configure.recentValues.size() == 0) return;
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							modelAutoCompleted.clear();
							for (String str : configure.recentValues){
								modelAutoCompleted.addElement(str);
							}
						}
					});
					autoCompleted.setVisibleRowCount(configure.recentValues.size());
					showCompleted();
					return;
				}
				process.add(new Thread(){
					public void run(){
						try {
							suggesting();
							final List<String> strs = youtube.suggest(value.getText());
							modelAutoCompleted.clear();
							if (!strs.isEmpty()){
								SwingUtilities.invokeLater(new Runnable() {
									public void run() {
										for (String str : strs){
											modelAutoCompleted.addElement(str);
										}
										autoCompleted.setVisibleRowCount(strs.size());
										if (!configure.lastValueSong.equals(value.getText())) showCompleted();
									}
								});
							}else{
								completer.setVisible(false);
							}
							stopSuggesting();
						} catch (IOException e) {
							stopSuggesting();
							setStatus("NETWORK ERROR. CAN'T GET SUGGEST FOR THIS VALUE");
							e.printStackTrace();
						}
					}
				});
				synchronized (suggetThread) {
					suggetThread.notifyAll();
				}
			}
		});
		JScrollPane scrollPane = new JScrollPane(autoCompleted, JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
		scrollPane.setBorder(null);
		autoCompleted.setFocusable(false);
		autoCompleted.setFixedCellHeight(25);
		autoCompleted.setFont(autoCompleted.getFont().deriveFont(15f));
		autoCompleted.setBorder(null);
		autoCompleted.setCellRenderer(new SuggestRenderer());
//		completer.setBorder(null);
		autoCompleted.addMouseListener(new MouseAdapter() {
			public void mousePressed(MouseEvent e){
				value.setText(autoCompleted.getSelectedValue().toString());
				search();
				completer.setVisible(false);
			}
			
			public void mouseExited(MouseEvent e){
				completer.setVisible(false);
			}
		});
		autoCompleted.addMouseMotionListener(new MouseAdapter() {
			public void mouseMoved(MouseEvent e){
				int index = autoCompleted.locationToIndex(e.getPoint());
				if (index != -1){
					autoCompleted.setSelectedIndex(index);
				}
			}
		});
		scrollPane.setFocusable(false);
		scrollPane.getHorizontalScrollBar().setFocusable(false);
		scrollPane.getVerticalScrollBar().setFocusable(false);
		completer = new JPopupMenu();
		completer.add(scrollPane);
		value.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e){
				if (!SwingUtilities.isLeftMouseButton(e)) return;
				if (modelAutoCompleted.isEmpty()){
					if (configure.recentValues.size() == 0) return;
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							for (String str : configure.recentValues){
								modelAutoCompleted.addElement(str);
							}
							completer.setPreferredSize(autoCompleted.getPreferredSize());
							autoCompleted.setVisibleRowCount(configure.recentValues.size());
						}
					});
				}
				showCompleted();
			}
		});
		panelSearch.add(search = new JButton("Go"));
		search.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				search();
			}
		});
		panelSearch.add(Box.createHorizontalGlue());
		scrollPaneRecent = new JScrollPane(recentSongs = new JList(modelRecentSongs = new DefaultListModel()), JScrollPane.VERTICAL_SCROLLBAR_NEVER, JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
		scrollPaneRecent.setBorder(null);
		final JPopupMenu popupRecent = createPopupMenu(recentSongs);
		popupRecentSongs = new JPopupMenu(){
			private static final long serialVersionUID = 1494030483170969147L;

			@Override
			public void setVisible(boolean b) {
				if (popupRecent.isVisible()){
					super.setVisible(true);
					return;
				}else{
					super.setVisible(false);
				}
				super.setVisible(b);
			}
		};
		popupRecentSongs.addMouseListener(new MouseAdapter() {
			@Override
			public void mouseExited(MouseEvent e) {
				popupRecent.setVisible(false);
//				popupRecentSongs.setVisible(false);
			}
		});
		recentSongs.setVisibleRowCount(1);
		recentSongs.setLayoutOrientation(JList.HORIZONTAL_WRAP);
		recentSongs.setFixedCellWidth(300);
		recentSongs.setCellRenderer(new SongRenderer());
		recentSongs.addMouseListener(new MouseAdapter() {
			public void mouseClicked(MouseEvent e){
				if (SwingUtilities.isRightMouseButton(e)) {
					if (recentSongs.getSelectedIndices().length <= 1){
						if (!modelRecentSongs.isEmpty()) recentSongs.setSelectedIndex(recentSongs.locationToIndex(e.getPoint()));
					}
				}else{
					if (e.getClickCount() == 2) {
						play((Video)recentSongs.getSelectedValue());
					}
				}
			}
			
			@Override
			public void mousePressed(MouseEvent e) {
				if (SwingUtilities.isLeftMouseButton(e)){
					popupRecent.setVisible(false);
					return;
				}
				popupRecent.show(null, e.getXOnScreen(), e.getYOnScreen());
			}
		});
		recentSongs.addKeyListener(new KeyAdapter() {
			@Override
			public void keyPressed(KeyEvent arg0) {
				if (arg0.getKeyCode() == KeyEvent.VK_ENTER) {
					play((Video)recentSongs.getSelectedValue());
				}
			}
		});
		
		popupRecentSongs.add(scrollPaneRecent);
		final JLabel labelRecent = new JLabel(getImage("recent.png"));
		labelRecent.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				dimension = panelSearch.getSize();
				dimension.height = 110;
				dimension.width -= 5;
				scrollPaneRecent.setPreferredSize(dimension);
				scrollPaneRecent.setMaximumSize(dimension);
				scrollPaneRecent.setMinimumSize(dimension);
				scrollPaneRecent.setSize(dimension);
				SwingUtilities.invokeLater(new Runnable() {
					public void run() {
						modelRecentSongs.clear();
						for (Video song : configure.recentSongs){
							modelRecentSongs.addElement(song);
						}
					}
				});
				if (!popupRecentSongs.isVisible()) {
					popupRecentSongs.show(panelSearch, 0, panelSearch.getPreferredSize().height);
				}else{
					popupRecentSongs.setVisible(false);
				}
			}
		});
		labelRecent.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		panelSearch.add(labelRecent);
		panelSearch.add(Box.createRigidArea(new Dimension(10, 1)));
		popupConfigure = new JPopupMenu();
		final JCheckBoxMenuItem itemDisplayOverlay = new JCheckBoxMenuItem("Display title", configure.displayOverlay);
		itemDisplayOverlay.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				configure.displayOverlay = itemDisplayOverlay.isSelected();
				setOverlay();
			}
		});
		final JCheckBoxMenuItem itemPlayRelative = new JCheckBoxMenuItem("Play in relative songs");
		itemPlayRelative.setSelected(configure.isPlayRelative);
		itemPlayRelative.addActionListener(new ActionListener() {
			@Override
			public void actionPerformed(ActionEvent arg0) {
				configure.isPlayRelative = itemPlayRelative.isSelected();
			}
		});
		popupConfigure.add(itemDisplayOverlay);
		popupConfigure.add(itemPlayRelative);
		final JLabel labelConfigure = new JLabel(getImage("conf.png"));
		labelConfigure.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				popupConfigure.show(labelConfigure, 0, labelConfigure.getPreferredSize().height + 5);
			}
		});
		labelConfigure.setCursor(Cursor.getPredefinedCursor(Cursor.HAND_CURSOR));
		panelSearch.add(labelConfigure);
		return panelSearch;
	}
	
	private void showCompleted(){
		dimension = value.getPreferredSize();
		dimension.height = completer.getPreferredSize().height;
//		completer.setPopupSize(dimension);
		completer.setPreferredSize(dimension);
		autoCompleted.setSelectedValue(value.getText(), true);
		if (!completer.isVisible()){
			completer.show(value, 0, value.getHeight());
		}else{
			completer.invalidate();
			completer.validate();
		}
		value.requestFocus();
	}
	
	public void setIconStatus(boolean set){
		iconStatus.setVisible(set);
	}
	
	private void updateSong(){
		setIconStatus(true);
		songInfo.setText("Updating song...");
		updateSong = new Thread(){
			List<Video> lists = new ArrayList<Video>();
			public void run(){
				configure.lastPageSong++;
				setTitle("Updating...");
				try {
					lists = youtube.search(configure.lastValueSong, configure.lastPageSong, "");
					configure.songs.addAll(lists);
					SwingUtilities.invokeLater(new Runnable() {
						public void run(){
							for (Video song : lists) {
								modelSongs.addElement(song);
							}
						}
					});
					songInfo.setText(configure.status);
				} catch (IOException e) {
					setStatus("NETWORK ERROR. CAN'T UPDATE SEARCH RESULT");
					e.printStackTrace();
				}
				updateSong = null;
				setIconStatus(false);
				setTitle(configure.title);
			}
		};
		updateSong.start();
	}
	
	private void updateSongOfUser(){
		setIconStatus(true);
		songInfo.setText("Updating song of username...");
		updateSong = new Thread(){
			List<Video> lists = new ArrayList<Video>();
			public void run(){
				configure.lastPageSong++;
				setTitle("Updating...");
				try {
					lists = youtube.userVideos(userId, configure.lastPageSong, "");
					configure.songs.addAll(lists);
					SwingUtilities.invokeLater(new Runnable() {
						public void run(){
							for (Video song : lists) {
								modelSongs.addElement(song);
							}
						}
					});
					songInfo.setText(configure.status);
				} catch (IOException e) {
					setStatus("NETWORK ERROR. CAN'T UPDATE VIDEOS THIS USERNAME");
					e.printStackTrace();
				}
				updateSong = null;
				setIconStatus(false);
				setTitle(configure.title);
			}
		};
		updateSong.start();
	}
	
	private void updateSongOfPlaylist(){
		setIconStatus(true);
		songInfo.setText("Updating song of playlist...");
		updateSong = new Thread(){
			List<Video> lists = new ArrayList<Video>();
			public void run(){
				configure.lastPageSong++;
				setTitle("Updating...");
				try {
					lists = youtube.getPlaylist(idPlaylist, configure.lastPageSong, "");
					configure.songs.addAll(lists);
					SwingUtilities.invokeLater(new Runnable() {
						public void run(){
							for (Video song : lists) {
								modelSongs.addElement(song);
							}
						}
					});
					songInfo.setText(configure.status);
				} catch (IOException e) {
					setStatus("NETWORK ERROR. CAN'T UPDATE VIDEOS THIS PLAYLIST");
					e.printStackTrace();
				}
				updateSong = null;
				setIconStatus(false);
				setTitle(configure.title);
			}
		};
		updateSong.start();
	}
	
	private void updateRelativeSong(){
		setIconStatus(true);
		songInfo.setText("Updating relative song...");
		updateRelativeSong = new Thread(){
			List<Video> lists = new ArrayList<Video>();
			public void run(){
				lastPageRelativeSong++;
				setTitle("Updating...");
				try {
					lists = youtube.relativeOfVideo(lastValueRelativeSong, lastPageRelativeSong, "");
					SwingUtilities.invokeLater(new Runnable() {
						public void run(){
							for (Video song : lists) {
								modelRelativeSongs.addElement(song);
							}
						}
					});
					songInfo.setText(configure.status);
				} catch (IOException e) {
					setStatus("NETWORK ERROR. CAN'T UPDATE RELATIVE VIDEOS");
					e.printStackTrace();
				}
				updateRelativeSong = null;
				setIconStatus(false);
				setTitle(configure.title);
			}
		};
		updateRelativeSong.start();
	}
	
	public void play(){
		currentIndexSong = songs.getSelectedIndex();
		if (currentIndexSong == -1) return;
		play((Video)songs.getSelectedValue());
	}
	
	public void play(final Video song){
		currentSong = song;
		configure.recentSongs.addFirst(song);
		new Thread(){
			public void run(){
				try {
					setIconStatus(true);
					playerComponent.setTitle(song.title.trim(), "Lượt xem : " + song.viewCount + " | Thời lượng : " + song.duration + " | Upload bởi: " + song.username + "<br/>" + song.description);
					if (storyBoard != null) storyBoard.clear();
					storyBoard = null;
					slider.toolTip.setImage(null);
					slider.max = 1;
					slider.plusDuration = 0;
					slider.setValue(0);
					slider.setRange(0);
					startDuration.setText("00:00");
					endDuration.setText("00:00");
					overlay.setTitle(null, null);
					player.stop();
					String link = song.getOriginLink(configure.format);
					if (link != null){
						player.playMedia(link);
//						player.playMedia(link, ":input-slave=" + song.getOriginLink(Format.AUDIO_256));
						player.getMediaMeta().setTitle(song.title);
						storyBoard = song.getStoryBoard();
						songInfo.setText(song.title + " [" + song.currentFormat + "]");
						new Thread(){
							public void run(){
								try {
									final List<Video> songs = youtube.relativeOfVideo(song.link, 1, "");
									SwingUtilities.invokeLater(new Runnable() {
										public void run() {
											modelRelativeSongs.clear();
											for(Video song: songs){
												modelRelativeSongs.addElement(song);
											}
											if (!songs.isEmpty()){
												relativeSongs.setSelectedIndex(0);
												relativeSongs.ensureIndexIsVisible(0);
											}
											lastPageRelativeSong = 1;
											lastValueRelativeSong = song.link;
										}
									});
								}catch (Exception e) {
									setStatus("NETWORK ERROR. CAN'T GET RELATIVE VIDEOS");
									e.printStackTrace();
								}
							}
						}.start();
					}else{
						setStatus("CANT'T GET DIRECT LINK");
					}
					setIconStatus(false);
				} catch (IOException e) {
					e.printStackTrace();
					setIconStatus(false);
					setStatus("NETWORK ERROR");
				}
			}
		}.start();
	}
	
	private void setStoryQuality(){
		switch (configure.qualitySoryBoard) {
			case 3: storyBoardQuality.setIcon(getImage("lager.png")); break;
			case 2: storyBoardQuality.setIcon(getImage("medium.png")); break;
			case 1: storyBoardQuality.setIcon(getImage("small.png")); break;
			case 0: storyBoardQuality.setIcon(getImage("not.png")); break;
			default: storyBoardQuality.setIcon(getImage("medium.png")); break;
		}
	}
	
	public JPopupMenu createPopupMenu(final JList type){
		JPopupMenu popupMenu = new JPopupMenu();
		
		JMenu menuCopy = new JMenu("Copy direct links");
		menuCopy.setIcon(getImage("copy.png"));
		for (final Format format : Format.values()){
			JMenuItem item = new JMenuItem(format.toString());
			item.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					getDirectLink(format, type);
				}
			});
			if (format.ordinal() == 7 || format.ordinal() == 10){
				menuCopy.addSeparator();
			}
			menuCopy.add(item);
		}
		JMenu saveDirectLink = new JMenu("Save direct links (*.m3u)");
		saveDirectLink.setIcon(getImage("m3u.png"));
		for (final Format format : Format.values()){
			JMenuItem item = new JMenuItem(format.toString());
			item.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent e) {
					saveAsPlaylist(format, type);
				}
			});
			saveDirectLink.add(item);
		}
		JMenuItem itemCopyLink = new JMenuItem("Copy links");
		itemCopyLink.setIcon(getImage("copy.png"));
		itemCopyLink.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				new Thread(){
					public void run(){
						Object[] save = type.getSelectedValues();
						String saveString = "";
						setStatus("COPYING LINK: 0/" + save.length);
						for (int i = 0; i < save.length; i++){
							saveString += "http://www.youtube.com/watch?v=" + ((Video)save[i]).link + (i == save.length - 1 ? "" : "\n");
							setStatus("COPYING LINK: " + (i + 1) + "/" + save.length);
						}
						clipboard.setContents(new StringSelection(saveString), null);
						setStatus("COMPLETE COPY: " + save.length + "/" + save.length);
					}
				}.start();
			}
		});
		JMenuItem itemSearch = new JMenuItem("Search this title");
		itemSearch.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (type.getSelectedIndex() == -1) return;
				value.setText(((Video)type.getSelectedValue()).title);
				search();
			}
		});
		JMenuItem itemVideoOfUser = new JMenuItem("Get videos this username");
		itemVideoOfUser.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (type.getSelectedIndex() == -1) return;
				new Thread(){
					public void run(){
						try {
							setIconStatus(true);
							setStatus("GET VIDEO OF USERNAME");
							setSongs(youtube.userVideos(userId = ((Video)type.getSelectedValue()).userId, 1, ""), true);
							configure.lastPageSong  = 1;
							currentAction = ACTION_GET_VIDEO_OF_USERNAME;
							songInfo.setText(configure.status);
							setIconStatus(false);
						}catch (IOException e) {
							setStatus("NETWORK ERROR");
							setIconStatus(false);
							e.printStackTrace();
						}
					}
				}.start();
			}
		});
		JMenuItem itemOpen = new JMenuItem("Open in browser");
		itemOpen.setIcon(getImage("chrome.png"));
		itemOpen.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				if (type.getSelectedIndex() == -1) return;
				openLink("http://www.youtube.com/watch?v=" + ((Video)type.getSelectedValue()).link);
			}
		});
		JMenuItem itemShare = new JMenuItem("Share to facebook");
		itemShare.setIcon(getImage("facebook.png"));
		itemShare.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				if (type.getSelectedIndex() == -1) return;
				shareFacebook("http://www.youtube.com/watch?v=" + ((Video)type.getSelectedValue()).link);
			}
		});
		JMenuItem itemGetPlaylist = new JMenuItem("Get song of playlist");
		itemGetPlaylist.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				String id = JOptionPane.showInputDialog(YouTube.this, "Enter id of playlist", "");
				if (id == null || id.equals("")) return;
			}
		});
		JMenuItem itemSort = new JMenuItem("Sort by name");
		itemSort.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				new Thread(){
					public void run(){
						Comparator<Video> sortName = new Comparator<Video>() {
							public int compare(Video one, Video two) {
								return one.title.compareTo(two.title);
							}
						};
						Collections.sort(configure.songs, sortName);
						setSongs(configure.songs, true);
					}
				}.start();
			}
		});
		popupMenu.add(menuCopy);
		popupMenu.add(saveDirectLink);
		popupMenu.add(itemCopyLink);
		popupMenu.add(itemSearch);
		popupMenu.add(itemVideoOfUser);
		popupMenu.add(itemOpen);
		popupMenu.add(itemShare);
		popupMenu.add(itemGetPlaylist);
		popupMenu.add(itemSort);
		return popupMenu;
	}
	
	private JPopupMenu createPopupMenuSong(){
		JPopupMenu popupMenu = createPopupMenu(songs);
		JMenu menuView = new JMenu("View mode");
		menuView.setIcon(getImage("viewmode.png"));
		JMenuItem modeDetail = new JMenuItem("Detail");
		modeDetail.setIcon(getImage("viewmode_detail.png"));
		menuView.add(modeDetail);
		JMenuItem modeClassic = new JMenuItem("Classic");
		modeClassic.setIcon(getImage("viewmode_classic.png"));
		menuView.add(modeClassic);
		JMenuItem itemDelete = new JMenuItem("Delete");
		itemDelete.setIcon(getImage("delete.png"));
		itemDelete.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent e) {
				deleteLinks();
			}
		});
		popupMenu.add(itemDelete);
		popupMenu.addSeparator();
		popupMenu.add(menuView);
		return popupMenu;
	}
	
	private JPopupMenu createPopupPlayer(){
		JPopupMenu popupMenu = new JPopupMenu();
		JMenu menuAspectRatio = new JMenu("Aspect Ratio");
//		menuAspectRatio.setOpaque(true);
		String[] aspectRatios = new String[]{"16:9", "16:10", "4:3", "185:100", "221:100", "235:100", "239:100", "5:3", "5:4", "1:1"};
		JMenuItem itemDefault = new JMenuItem("Default");
		itemDefault.addActionListener(new ActionListener() {
			public void actionPerformed(ActionEvent arg0) {
				player.setAspectRatio(null);
			}
		});
		menuAspectRatio.add(itemDefault);
		for (final String aspectRatio : aspectRatios){
			JMenuItem item = new JMenuItem(aspectRatio);
			item.addActionListener(new ActionListener() {
				public void actionPerformed(ActionEvent arg0) {
					player.setAspectRatio(aspectRatio);
				}
			});
			menuAspectRatio.add(item);
		}
		popupMenu.add(menuAspectRatio);
		return popupMenu;
	}
	
	public void getDirectLink(final Format format, final JList type){
		new Thread(){
			public void run(){
				Object[] save = type.getSelectedValues();
				String saveString = "";
				setStatus("COPYING LINK: 0/" + save.length);
				for (int i = 0; i < save.length; i++){
					try {
						saveString += (((Video)save[i])).getOriginLink(format) + (i == (save.length - 1) ? "" : "\n");
						setStatus("COPYING LINK: " + (i + 1) + "/" + save.length);
					} catch (IOException e) {
						setStatus("NETWORK ERROR");
						e.printStackTrace();
					}
				}
				clipboard.setContents(new StringSelection(saveString), null);
				setStatus("COMPLETE COPY: " + save.length + "/" + save.length);
			}
		}.start();
	}
	
	public void deleteLinks(){
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				final int[] remove = songs.getSelectedIndices();
				for (int i = remove.length - 1; i >= 0; i--) {
					modelSongs.remove(remove[i]);
					configure.songs.remove(remove[i]);
				}
			}
		});
	}
	
	private void shareFacebook(String link) {
	    try {
			openLink("http://www.facebook.com/sharer.php?u=" + URLEncoder.encode(link, "UTF-8"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
	}
	
	private void openLink(String link) {
	    if (Desktop.isDesktopSupported()) {
	        try {
				Desktop.getDesktop().browse(new URI(link));
			} catch (Exception e) {
				e.printStackTrace();
			}
	    }
	}
	
	private void saveAsPlaylist(final Format format, final JList type) {
		final String str = showSave("Save as playlist", new File(configure.oldFolder), "Youtube Direct Link.m3u");
		if (str == null) return;
		new Thread() {
			public void run() {
				try {
					Object[] songs = type.getSelectedValues();
					BufferedWriter out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(str),"UTF-8"));
					setStatus("SAVING LINK: 0/" + songs.length);
					out.write("#EXTM3U");
					out.newLine();
					for (int i = 0; i < songs.length; i++) {
						Video song = ((Video)songs[i]);
						out.write("#EXTINF:-1," + song.getTitle());
						out.newLine();
						out.write(song.getOriginLink(format));
						out.newLine();
						setStatus("SAVING LINK: " + (i + 1) + "/" + songs.length);
					}
					out.flush();
					out.close();
					setStatus("COMPLETE LINK: " + songs.length + "/" + songs.length);
					JOptionPane.showMessageDialog(YouTube.this, "Save direct link is completed.", "Save...", JOptionPane.INFORMATION_MESSAGE);
				} catch (Exception e1) {
					setStatus("NETWORK ERROR");
					e1.printStackTrace();
				}
			}
		}.start();
	}
	
	private String showSave(String title, File currentFolder, String fileSelected){
		chooser.setFileSelectionMode(JFileChooser.FILES_ONLY);
		chooser.setDialogTitle(title);
		chooser.setCurrentDirectory(currentFolder);
		chooser.setSelectedFile(new File(fileSelected));
		int ret = chooser.showSaveDialog(YouTube.this);
		if(ret == JFileChooser.APPROVE_OPTION){
			configure.oldFolder = chooser.getCurrentDirectory().getAbsolutePath();
			return chooser.getSelectedFile().getAbsolutePath();
		}
		return null;
	}
	
	public void setKeys(){
		Toolkit.getDefaultToolkit().addAWTEventListener(new AWTEventListener() {
			public void eventDispatched(AWTEvent event) {
				if (event instanceof KeyEvent && !(event.getSource() instanceof JTextField)) {
					KeyEvent keyEvent = (KeyEvent) event;
					if (keyEvent.getID() == KeyEvent.KEY_PRESSED) {
						switch (keyEvent.getKeyCode()) {
							case KeyEvent.VK_F: toggleFullScreen(); break;
							case KeyEvent.VK_ESCAPE: player.setFullScreen(false); break;
							case KeyEvent.VK_F1: showResult(); break;
							case KeyEvent.VK_F2: showRelative(); break;
							case KeyEvent.VK_F3: showSearch(); break;
							case KeyEvent.VK_F4: showStatus(); break;
							case KeyEvent.VK_SPACE: togglePlay(); break;
						}
					}
				}
			}
		}, AWTEvent.KEY_EVENT_MASK);
		
		KeyStroke undo = KeyStroke.getKeyStroke('Z', KeyEvent.CTRL_DOWN_MASK);
        actionMap.put(undo, new AbstractAction("undo") {
			public void actionPerformed(ActionEvent e) {
				if (history.undo()){
					setSongs(history.result, true);
				}
			}
            private static final long serialVersionUID = 0x3c3e890abe343253L;
        });
        KeyStroke redo = KeyStroke.getKeyStroke('Y', KeyEvent.CTRL_DOWN_MASK);
        actionMap.put(redo, new AbstractAction("redo") {
			private static final long serialVersionUID = 8379078255066171149L;
			public void actionPerformed(ActionEvent e) {
				if (history.redo()){
					setSongs(history.result, true);
				}
			}
        });
        KeyStroke volumeUp = KeyStroke.getKeyStroke('E', KeyEvent.CTRL_DOWN_MASK + KeyEvent.SHIFT_DOWN_MASK);
        actionMap.put(volumeUp, new AbstractAction("volumeUp") {
			private static final long serialVersionUID = 8379078255066171149L;
			public void actionPerformed(ActionEvent e) {
				addVolume(5);
			}
        });
        KeyStroke volumeDown = KeyStroke.getKeyStroke('W', KeyEvent.CTRL_DOWN_MASK + KeyEvent.SHIFT_DOWN_MASK);
        actionMap.put(volumeDown, new AbstractAction("volumeDown") {
			private static final long serialVersionUID = 8379078255066171149L;
			public void actionPerformed(ActionEvent e) {
				addVolume(-5);
			}
        });
        KeyboardFocusManager kfm = KeyboardFocusManager.getCurrentKeyboardFocusManager();
        kfm.addKeyEventDispatcher(new KeyEventDispatcher() {
			public boolean dispatchKeyEvent(KeyEvent e) {
				KeyStroke keyStroke = KeyStroke.getKeyStrokeForEvent(e);
				if (actionMap.containsKey(keyStroke)){
					final Action action = (Action)actionMap.get(keyStroke);
					final ActionEvent actionEvent = new ActionEvent(e.getSource(), e.getID(), null);
					SwingUtilities.invokeLater(new Runnable() {
						public void run() {
							action.actionPerformed(actionEvent);
						}
					});
				}
				return false;
			}
        });
	}
	
	private void showResult(){
		scrollPane.setVisible(!scrollPane.isVisible());
		invalidate();
		validate();
		requestFocus();
	}
	
	private void showRelative(){
		scrollPaneRelative.setVisible(!scrollPaneRelative.isVisible());
		invalidate();
		validate();
		requestFocus();
	}
	
	private void showSearch(){
		panelSearch.setVisible(!panelSearch.isVisible());
		invalidate();
		validate();
		requestFocus();
	}
	
	private void showStatus(){
		panelStatus.setVisible(!panelStatus.isVisible());
		invalidate();
		validate();
		requestFocus();
	}
	
	private void addVolume(int add){
		int volume = player.getVolume() + add;
		if (volume < 0){
			volume = 0;
		}else if (volume > 100){
			volume = 100;
		}
		player.setVolume(volume);
		overlay.setInformation("Volume: " + player.getVolume() + " %");
	}
	
	private void togglePlay() {
		if (player.isPlaying()){
			player.pause();
		}else if (player.isPlayable()){
			player.play();
		}
	}
	
	public void suggesting(){
		searchSuggest = true;
		synchronized (suggesting) {
			suggesting.notifyAll();
		}
	}
	
	public void stopSuggesting(){
		searchSuggest = false;
	}
	
	public void toggleFullScreen(){
		boolean playing = player.isPlaying();
		if (fullScreen){
			player.stop();
			frameFullScreen.setVisible(false);
			panelPlayer.add(playerComponent, BorderLayout.CENTER);
			YouTube.this.setVisible(true);
			player.setVideoSurface(videoSurface);
			if(playing){
				slider.plusDuration = slider.getValue();
				player.playMedia(player.mrl().replaceFirst("&begin=.*", "") + "&begin=" + slider.plusDuration);
			}
			requestFocus();
		}else{
			player.stop();
			frameFullScreen.getContentPane().add(playerComponent);
			frameFullScreen.setVisible(true);
			player.setVideoSurface(videoSurface);
			if(playing){
				slider.plusDuration = slider.getValue();
				player.playMedia(player.mrl().replaceFirst("&begin=.*", "") + "&begin=" + slider.plusDuration);
			}
		}
		playerComponent.repaint();
		setOverlay();
		fullScreen = !fullScreen;
	}
	
	private void setOverlay(){
		player.setOverlay(overlay);
		player.enableOverlay(true);
	}
	
	public static void main(String[] args) {
		SwingUtilities.invokeLater(new Runnable() {
			public void run() {
				try {
					UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
					new YouTube();
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
	}
}
